﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Markup;

using agree.configuration;
using agree.schema;

using alib.Collections;
using alib.Collections.ReadOnly;
using alib.Concurrency;
using alib.Debugging;
using alib.Enumerable;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class IrregsReader : ReadFile
	{
		public IrregsReader(IRtParent parent, String filename)
			: base(parent, filename)
		{
		}

		public IrregsReader(IRtParent parent)
			: base(parent, default(String))
		{
		}

		public override Object ProvideValue(IServiceProvider sp)
		{
			var arr = pre_fetch.Result
				.Trim(' ', '\"', '\t', '\r', '\n')
				.Split(alib.Character.Charset.cr_lf, StringSplitOptions.RemoveEmptyEntries)
				.Select(s => new IrregInfo(s))
				.ToArray();
			return arr;
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{_rule_name} {_infl} {_stem} {_rule}")]
	public sealed class IrregInfo
	{
		public IrregInfo(String irreg_line)
		{
			var data = irreg_line.Split(default(Char[]), StringSplitOptions.RemoveEmptyEntries);
			if (data.Length != 3)
				throw new GrammarAuxiliaryFileException("invalid irregular inflection specification");
			this._infl = data[0];
			this._rule_name = data[1];
			this._stem = data[2];
		}
		public IrregInfo(LexicalRule rule)
		{
			this._rule = rule;
		}
		public IrregInfo()
		{
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public String _infl;
		public String Inflected
		{
			get { return this._infl; }
			set { this._infl = value; }
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public String _rule_name;
		public String RuleName
		{
			get { return this._rule_name; }
			set
			{
				if (_rule == null)
					this._rule_name = value;
				else if (value != _rule.Name)
					throw new PropertyAlreadySetException("RuleName");
			}
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public String _stem;
		public String Stem
		{
			get { return this._stem; }
			set { this._stem = value; }
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public LexicalRule _rule;
		public LexicalRule LexicalRule
		{
			get { return this._rule; }
			set
			{
				if (_rule_name == null)
					_rule_name = value.Name;
				else if (_rule_name != value.Name)
					throw new GrammarAuxiliaryFileException("The rule '{0}' does not match the rule name '{1}'", value.Name, _rule_name);
				this._rule = value;
			}
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[ContentProperty("Items")]
	public sealed class Irregulars : identity_named_init, ISupportInitializeNotification,
		ρρEntryMgr
	{
		public Irregulars(IRtParent parent)
			: base(parent, parent)
		{
		}

		///////////////////////////////////////////////////////////////////////
		///
		EntryMgr em;
		public EntryMgr EntryMgr
		{
			get { return em; }
			set { em = value; }
		}
		///
		///////////////////////////////////////////////////////////////////////

		public ReadOnlyDictionary<String, RuleSurf[]> irreg_fwd;

		public ReadOnlyDictionary<String, RuleSurf[]> irreg_rev;

		IrregInfo[] irregs;
		public IList<IrregInfo> Items
		{
			get { return this.irregs; }
			set { this.irregs = value as IrregInfo[] ?? value.ToArray(); }
		}

		[DebuggerDisplay("{rule} {surf}")]
		public struct RuleSurf
		{
			public RuleSurf(LexicalRule rule, String surf)
			{
				this.rule = rule;
				this.surf = surf;
			}
			public LexicalRule rule;
			public String surf;
		};

		public void EndInit()
		{
			if (em == null)
				throw new RequiredPropertyNotSetException("EntryMgr");

			if (irregs != null && irregs.Length > 0)
				_load_irreg_indexes(em.AgreeConfig.Lexicon.LexicalComparison);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void _load_irreg_indexes(IEqualityComparer<String> lex_compare)
		{
			int i, c;
			c = irregs.Length;
			var fwd = new Dictionary<String, RuleSurf[]>(c, lex_compare);
			var rev = new Dictionary<String, RuleSurf[]>(c, lex_compare);
			for (i = 0; i < c; i++)
			{
				String s_rule = irregs[i]._rule_name;

				Entry e;
				if (!em.entry_dict.TryGetValue(s_rule, out e))
					throw new GrammarAuxiliaryFileException("Rule '{0}' listed as a rule in the irregs file is not a recognized rule.", s_rule);

				LexicalRule lr = e as LexicalRule;
				if (lr == null)
					throw new GrammarAuxiliaryFileException("Rule '{0}' listed as a rule in the irregs file is not an inflection rule.", s_rule);

				irregs[i]._rule = lr;
				lr.SetIsSpellingChange();

				RuleSurf[] rgrs, tmp;
				String infl = irregs[i]._infl;
				String stem = irregs[i]._stem;

				if (fwd.TryGetValue(infl, out tmp))
				{
					rgrs = new RuleSurf[tmp.Length + 1];
					rgrs[0] = new RuleSurf(lr, stem);
					tmp.CopyTo(rgrs, 1);
				}
				else
					rgrs = new[] { new RuleSurf(lr, stem) };
				fwd[infl] = rgrs;

				if (rev.TryGetValue(stem, out tmp))
				{
					rgrs = new RuleSurf[tmp.Length + 1];
					rgrs[0] = new RuleSurf(lr, infl);
					tmp.CopyTo(rgrs, 1);
				}
				else
					rgrs = new[] { new RuleSurf(lr, infl) };
				rev[stem] = rgrs;
			}
			irreg_fwd = fwd.ToReadOnlyDictionary();
			irreg_rev = rev.ToReadOnlyDictionary();
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class MorphologyRules : identity_named_init, ISupportInitializeNotification,
		ρρIrregulars,
		ρρEntryMgr
	{
		public MorphologyRules(IRtParent parent)
			: base(parent, parent)
		{
		}

		///////////////////////////////////////////////////////////////////////
		///
		EntryMgr em;
		public EntryMgr EntryMgr
		{
			get { return em; }
			set { em = value; }
		}
		///
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		///
		public Irregulars irregs;
		public Irregulars Irregulars
		{
			get { return irregs; }
			set { irregs = value; }
		}
		/// 
		///////////////////////////////////////////////////////////////////////


		public MorphologicalRule[] morph_lexrules;

		public LexicalRule[] non_affixing_lexrules;

		public void EndInit()
		{
			var lrs = em._lexical_rules;

			int c_nmlr = lrs.Count - lrs.c_spelling_change;

			non_affixing_lexrules = new LexicalRule[c_nmlr];

			morph_lexrules = new MorphologicalRule[lrs.c_morphological];

			int i = 0, j = 0;
			foreach (var lr in em._lexical_rules)
			{
				var mr = lr as MorphologicalRule;
				if (mr != null)
					morph_lexrules[i++] = mr;

				if (!lr.IsSpellingChange)
					non_affixing_lexrules[j++] = lr;
			}
			Debug.Assert(i == lrs.c_morphological);
			Debug.Assert(j == c_nmlr);
		}
	}


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DisplayName("Lexicon")]
	[Description("A set of lexical entries.")]
	[ContentProperty("LexicalEntries")]
	public class Lexicon : has_grammar_base, ISupportInitializeNotification,
		ρρTokenizer,
		ρρMorphologyRules,
		ρρTdlHash
	{
		public Lexicon(IRtParent parent)
			: base(parent)
		{
		}

		public
		IEqualityComparer<String> lex_compare;

#if NON_INITIAL_MWE
		[DebuggerDisplay("ix: {index}  {lex_entry.ToString(),nq}")]
		public struct NonInitialMwe
		{
			public int index;
			public LexicalEntry lex_entry;
		};

		public struct WordNonInitialMwe
		{
			public WordNonInitialMwe(String word, int ix, LexicalEntry le)
			{
				this.word = word;
				this.nimwe = new NonInitialMwe { index = ix, lex_entry = le };
			}
			public String word;
			public NonInitialMwe nimwe;
		}

		public ILookup<String, NonInitialMwe> mwe_lookup;
#endif

		///////////////////////////////////////////////////////////////////////
		///
		Dictionary<String, LexicalEntry[]> lex_lookup;
		public LexicalEntry[] this[String s]
		{
			get
			{
				LexicalEntry[] rglx;
				lex_lookup.TryGetValue(s, out rglx);
				return rglx;
			}
		}
		/// 
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		///
		public MorphologyRules mph;
		public MorphologyRules MorphologyRules
		{
			get { return mph; }
			set { mph = value; }
		}
		/// 
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		///
		ITokenizer itok;
		public ITokenizer Tokenizer
		{
			get { return this.itok ?? em.DefaultTokenizer; }
			set { if (value != null) this.itok = value; }
		}
		///
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		///
		IReadOnlyList<LexicalEntry> lexical_entries;
		public IReadOnlyList<LexicalEntry> LexicalEntries
		{
			get { return lexical_entries; }
			set { lexical_entries = value; }
		}
		///
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		///
		IReadOnlyList<GenericLexicalEntry> generic_lexical_entries;
		public IReadOnlyList<GenericLexicalEntry> GenericLexicalEntries
		{
			get
			{
				return generic_lexical_entries;
				//EntrySet es;
				//EntryMgr em;
				//var gles = generic_lexical_entries;
				//if (gles == null && (es = lexical_entries as EntrySet) != null && (em = es.EntryMgr) != null)
				//	gles = em._generic_lexical_entries;
				//return gles ?? Collection<GenericLexicalEntry>.None;
			}
			set { generic_lexical_entries = value; }
		}
		///
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		///
		public long TdlHash
		{
			get
			{
				EntrySet es;
				long h = 0;
				if ((es = LexicalEntries as EntrySet) != null)
					h ^= es.TdlHash;
				if ((es = GenericLexicalEntries as EntrySet) != null)
					h ^= es.TdlHash;
				return h;
			}
		}
		///
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void EndInit()
		{
			var em = this.EntryMgr;
			if (em == null)
				throw new RequiredPropertyNotSetException("EntryMgr");

			//lex_compare = AgreeConfig.Lexicon.LexicalComparison;
			lex_compare = StringComparer.InvariantCultureIgnoreCase;	// fix fix fix

			if (lexical_entries == null)
				lexical_entries = (IReadOnlyList<LexicalEntry>)em._lexical_entries ?? Collection<LexicalEntry>.None;
			if (lexical_entries.Count == 0)
				throw new Warning("Lexicon initialized with no lexical entries");

			if (generic_lexical_entries == null)
				generic_lexical_entries = (IReadOnlyList<GenericLexicalEntry>)em._generic_lexical_entries ?? Collection<GenericLexicalEntry>.None;
			//if (lexical_entries.Count == 0)
			//	throw new Warning("Lexicon initialized with no lexical entries");

			/// index singleton orthographies and initial words of MWEs for parsing
			lex_lookup = LexicalEntries
						.GroupBy(le => le.words[0], lex_compare)
#if false
						.ToDictionary(grp => grp.Key, grp => grp.ToArray(), lex_compare);
#else
.ToDictionary(grp => grp.Key, grp =>
							{
								var rg = grp.ToArray();
								if (!rg.IsDistinct(le => le.Name))
									throw new Warning("Duplicate lexical entry '{0}'", rg.Select(le => le.Name).Duplicates().First());
								return rg;
							}, lex_compare);
#endif

#if NON_INITIAL_MWE
			/// index follow-on words of multi-word entries for parsing
			mwe_lookup = LexicalEntries
						.Where(le => le.words.Length > 1)
						.SelectMany(le => le.words.Skip(1).Select((String w, int ix) => new WordNonInitialMwe(w, ix + 1, le)))
						.ToLookup(a => a.word, a => a.nimwe, lex_compare);
#endif
		}
	};
}
